package com.hd123.rumba.gwt.widget2.server;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.UUID;
import java.util.Vector;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import com.hd123.rumba.exception.BusinessException;
import com.hd123.rumba.gwt.widget2.client.dialog.RUploadDialog;
import com.hd123.rumba.request.Dictionary;
import com.hd123.rumba.user.IUser;

/**
 * 文件的上传服务，配合{@link RUploadDialog}使用
 * 
 * @author LiQi
 * 
 */
public class UploadServlet extends HttpServlet {

  private static final long serialVersionUID = 1L;

  private Logger logger = Logger.getLogger(this.getClass());

  /** 上传文件列表，用于查询进度 */
  public static Vector<FileInfo> files = new Vector();

  /**
   * 处理上传
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    long maxFileSize = -1;
    try {
      // 登录检查
      checkLogin(request);

      // 检查上传请求是否正确
      boolean isMultipart = ServletFileUpload.isMultipartContent(request);
      if (!isMultipart)
        throw new BusinessException("错误请求");

      // 上传文件的绝对路径
      String realUploadFolderRoot = getRealUploadFolderRoot(request);

      // 绝对路径下的子文件夹
      String subFolder = getSubFolder();

      // 创建文件夹
      File folder = new File(realUploadFolderRoot + "/" + subFolder);
      folder.mkdirs();

      // 读取参数
      String fileId = request.getParameter(RUploadDialog.UPLOAD_REQUEST_KEY_FILEID);
      String maxFileSizeStr = request.getParameter(RUploadDialog.UPLOAD_REQUEST_KEY_MAX_FILESIZE);
      maxFileSize = maxFileSizeStr == null ? -1 : new Long(maxFileSizeStr).longValue();

      // 执行上传
      ServletFileUpload upload = new ServletFileUpload();
      if (maxFileSize != -1)
        upload.setFileSizeMax(maxFileSize);
      UploadProgressListener progressListener = new UploadProgressListener();
      upload.setProgressListener(progressListener);

      FileInfo fileInfo = null;
      for (FileItemIterator iter = upload.getItemIterator(request); iter.hasNext();) {
        FileItemStream item = iter.next();
        if (item.isFormField())
          continue;

        String fileName = extractFileName(item.getName());
        String remoteFileName = subFolder + "/" + fileName;
        fileInfo = new FileInfo();
        fileInfo.setId(fileId);
        fileInfo.setRemoteName(remoteFileName);
        progressListener.setFileInfo(fileInfo);
        files.add(fileInfo);

        InputStream uploadedStream = item.openStream();

        String realFullFileName = realUploadFolderRoot + "/" + subFolder + "/" + fileName;
        File uploadedFile = new File(realFullFileName);
        FileOutputStream fos = new FileOutputStream(uploadedFile);

        byte[] byteArray = new byte[500];
        int i = 0;
        while ((i = uploadedStream.read(byteArray)) != -1) {
          fos.write(byteArray, 0, i);
        }
        fos.flush();
        fos.close();
        uploadedStream.close();

        logger.info("上传文件成功. 服务端文件=[" + realFullFileName + "], 客户端文件=[" + item.getName() + "]");
        files.remove(fileInfo);
      }
      writeUploadReponse(response, true, null, fileInfo.getRemoteName());
    } catch (Exception e) {
      logger.error(e);
      String message = e.getCause() instanceof FileSizeLimitExceededException ? "文件大小超过"
          + maxFileSize + "字节的限制" : e.getMessage();
      writeUploadReponse(response, false, message, null);
    }
  }

  /**
   * 把上传结果写入reponse
   * 
   * @param response
   * @param success
   *          是否成功
   * @param message
   *          错误信息
   * @throws IOException
   */
  private void writeUploadReponse(HttpServletResponse response, boolean success, String message,
      String remoteFileName) throws IOException {
    JSONObject jsonResponse = new JSONObject();
    try {
		jsonResponse.put(RUploadDialog.UPLOAD_RESPONSE_KEY_SUCCESS, success);
		jsonResponse.put(RUploadDialog.UPLOAD_RESPONSE_KEY_MESSAGE, message);
	    jsonResponse.put(RUploadDialog.UPLOAD_RESPONSE_KEY_REMOTEFILENAME, remoteFileName);
	} catch (JSONException e) {
		throw new RuntimeException(e);
	}
    writeResponse(response, jsonResponse);
  }

  /**
   * 处理上传进度查询
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String fileId = request.getParameter(RUploadDialog.PROGRESS_REQUEST_KEY_FILEID);
    if (fileId == null)
      return;

    for (Iterator<FileInfo> i = files.iterator(); i.hasNext();) {
      FileInfo fileInfo = i.next();
      if (fileId.equals(fileInfo.getId())) {
        writeProgressResponse(response, true, fileInfo.getProgressPercent());
        return;
      }
    }
    writeProgressResponse(response, false, -1);
  }

  /**
   * 把进度查询结果写入response
   * 
   * @param response
   * @param found
   *          是否找到
   * @param percent
   *          百分比，[0,100]
   * @throws IOException
   */
  public void writeProgressResponse(HttpServletResponse response, boolean found, int percent)
      throws IOException {
    JSONObject jsonResponse = new JSONObject();
    try {
    	jsonResponse.put(RUploadDialog.PROGRESS_RESPONSE_KEY_FOUND, found);
        jsonResponse.put(RUploadDialog.PROGRESS_RESPONSE_KEY_PERCENT, percent);
	} catch (JSONException e) {
		throw new RuntimeException(e);
	}
    writeResponse(response, jsonResponse);
  }

  /**
   * 把JSONObject写入response
   * 
   * @param response
   * @param jsonResponse
   * @throws IOException
   */
  private void writeResponse(HttpServletResponse response, JSONObject jsonResponse)
      throws IOException {
    Writer w = new OutputStreamWriter(response.getOutputStream());
    w.write(jsonResponse.toString());
    w.close();
    response.setStatus(HttpServletResponse.SC_OK);
  }

  /**
   * 检查当前是否已经登录。
   * 
   * @param request
   * @throws BusinessException
   *           若还没有登录，则抛出异常
   */
  private void checkLogin(HttpServletRequest request) throws BusinessException {
    Hashtable sessionValues = (Hashtable) request.getSession().getAttribute(
        Dictionary.SESSIONMARKER);
    IUser sessionUser = sessionValues == null ? null : (IUser) sessionValues
        .get(Dictionary.CURRENTUSER);
    if (sessionUser == null)
      throw new BusinessException("尚未登录或长时间未使用系统。");
  }

  /**
   * 计算物理上传文件夹
   * 
   * @param request
   * @return
   */
  private String getRealUploadFolderRoot(HttpServletRequest request) throws BusinessException {
    // 读取参数
    String uploadFolderRoot = request
        .getParameter(RUploadDialog.UPLOAD_REQUEST_KEY_UPLOAD_FOLDER_ROOT);
    if (uploadFolderRoot == null || uploadFolderRoot.equals(""))
      throw new NullPointerException("传入参数" + RUploadDialog.UPLOAD_REQUEST_KEY_UPLOAD_FOLDER_ROOT
          + "允许为空");

    if (uploadFolderRoot.substring(1, 2).equals(":")) {
      // 传入的是物理路径，直接使用之
      return uploadFolderRoot;
    } else {
      // 传入的是相对路径，变换为绝对路径
      return request.getSession().getServletContext().getRealPath(uploadFolderRoot);
    }
  }

  /**
   * 生成根下面的子文件夹
   * 
   * @return
   */
  private String getSubFolder() {
    Date d = new Date();
    String month = new SimpleDateFormat("yyyyMM").format(d);
    String day = new SimpleDateFormat("yyyyMMdd").format(d);
    String currentFileFolder = new SimpleDateFormat("yyyyMMdd-HHmmss").format(d) + "-"
        + UUID.randomUUID();
    String result = month + "/" + day + "/" + currentFileFolder;

    return result;
  }

  /**
   * 从包含路径的文件名中剥离出文件名
   * 
   * @param fileNameWithPath
   * @return
   */
  private String extractFileName(String fileNameWithPath) {
    int position = fileNameWithPath.lastIndexOf("/");
    if (position == -1)
      position = fileNameWithPath.lastIndexOf("\\");

    if (position == -1)
      return fileNameWithPath;
    else
      return fileNameWithPath.substring(position + 1, fileNameWithPath.length());
  }

  /**
   * 上传进度监听器
   * 
   * @author LiQi
   * 
   */
  private class UploadProgressListener implements ProgressListener {

    /** 对应的文件 */
    private FileInfo fileInfo;

    public void update(long bytesRead, long contentLength, int items) {
      if (fileInfo != null) {
        fileInfo.setBytesRead(bytesRead);
        fileInfo.setContentLength(contentLength);
      }
    }

    public FileInfo getFileInfo() {
      return fileInfo;
    }

    public void setFileInfo(FileInfo fileInfo) {
      this.fileInfo = fileInfo;
    }
  }

  /**
   * 文件上传进度信息
   * 
   * @author LiQi
   * 
   */
  private class FileInfo {

    /** 文件的唯一标识，由客户端传来 */
    private String id;

    /** 服务端文件名 */
    private String remoteName;

    /** 已上传字节数 */
    private long bytesRead;

    /** 总长度 */
    private long contentLength;

    public String getId() {
      return id;
    }

    public void setId(String id) {
      this.id = id;
    }

    public String getRemoteName() {
      return remoteName;
    }

    public void setRemoteName(String remoteName) {
      this.remoteName = remoteName;
    }

    public long getBytesRead() {
      return bytesRead;
    }

    public void setBytesRead(long bytesRead) {
      this.bytesRead = bytesRead;
    }

    public long getContentLength() {
      return contentLength;
    }

    public void setContentLength(long contentLength) {
      this.contentLength = contentLength;
    }

    /**
     * 进度，取值[0,100]
     * 
     * @return
     */
    public int getProgressPercent() {
      return new Long(Math.round(bytesRead * 100.0d / contentLength)).intValue();
    }
  }
}
